/*            Copyright(c) 2006-2008 Dave Sexton           *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using DaveSexton.DocProject.Engine;
using System.Xml;

namespace DaveSexton.DocProject.Sandcastle
{
	internal sealed class VersionBuildStep : BuildStep<SandcastleBuildEngine>
	{
		#region Public Properties
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string DefaultVersionName = "[Default Version]";

		public override string Name
		{
			get
			{
				return Resources.Text.VersionBuildStepName;
			}
		}
		#endregion

		#region Private / Protected
		private readonly IDictionary<string, IList<SourceVersion>> versions;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="VersionBuildStep" /> class.
		/// </summary>
		public VersionBuildStep(SandcastleBuildEngine engine, IEnumerable<Source> sources, SourceVersionCollection sourceVersions, IDictionary<string, string> sourceVersionNames)
			: base(engine)
		{
			if (sources == null)
				throw new ArgumentNullException("sources");

			if (sourceVersions == null)
				throw new ArgumentNullException("sourceVersions");

			if (sourceVersions.Count == 0)
			{
				if (sourceVersionNames.Count == 0)
					throw new ArgumentException(Resources.Errors.VersionBuildStepNoSources, "sourceVersions");

				versions = new Dictionary<string, IList<SourceVersion>>(sourceVersionNames.Count);
			}
			else
			{
				versions = SourceVersion.GroupBy(false, s => s.Version.Trim(), sourceVersions, StringComparer.OrdinalIgnoreCase);
			}

			if (sourceVersionNames != null)
			// merge the project references and external sources into the versions dictionary using the sourceVersionNames dictionary
			{
				foreach (Source source in sources)
				{
					SystemPath path = source.Input;
					string key = SourceVersion.GenerateKey(engine.Settings.ProjectDirectory, path);
					string version = null;
					
					if (sourceVersionNames.ContainsKey(key))
						version = sourceVersionNames[key];

					// NOTE: Allow empty version strings here since a default will be supplied later when written to disc

					version = (version ?? "").Trim();

					IList<SourceVersion> list = null;

					if (versions.ContainsKey(version))
						list = versions[version];
					
					if (list == null)
						versions[version] = list = new List<SourceVersion>(1);

					list.Add(new SourceVersion(key, path, version));
				}
			}
		}
		#endregion

		#region Methods
		protected override void ExecuteInternal(BuildContext context)
		{
			context.TraceLine();
			context.TraceLine("Gathering version information...");

			BuildStepCollection steps = new BuildStepCollection(Engine);

			int versionCount = 0;

			SandcastleSettings settings = Engine.Settings;

			XmlWriterSettings writerSettings = new XmlWriterSettings();
			writerSettings.Indent = true;

			if (context.Canceled)
				return;

			using (XmlWriter contentWriter = XmlWriter.Create(settings.VersionsContentFile, writerSettings))
			{
				contentWriter.WriteStartDocument();
				contentWriter.WriteStartElement("content");
				contentWriter.WriteAttributeString("xml", "space", "", "preserve");

				string projectContentItem = "versions.project_name";

				WriteContentItem(contentWriter, projectContentItem, Engine.Options.DocumentationSetName);

				using (XmlWriter configWriter = XmlWriter.Create(settings.VersionsConfigFile, writerSettings))
				{
					configWriter.WriteStartDocument();
					configWriter.WriteStartElement("versions");
					configWriter.WriteStartElement("versions");		// NOTE: two version elements are required (presumably to support multiple projects simultaneously)
					configWriter.WriteAttributeString("name", projectContentItem);

					foreach (string version in versions.Keys)
					{
						if (context.Canceled)
							return;

						versionCount++;

						string value = version;

						// NOTE: version is already trimmed and will not be null
						if (value.Length == 0)
							value = DefaultVersionName;

						string spaceless = value.Replace(" ", "");

						// NOTE: Sandcastle uses the item IDs as NCNames
						string versionId = "vid." + XmlConvert.EncodeLocalName(spaceless);

						string reflectionFileName = PathFormat.EnsureFileName(spaceless);
						string reflectionFile = System.IO.Path.Combine(context.CurrentDirectory, reflectionFileName + ".org");

						WriteContentItem(contentWriter, versionId, value);
						WriteVersion(configWriter, versionId, reflectionFile);

						AddVersionBuildSteps(settings, steps, versions[version], reflectionFile);
					}

					configWriter.WriteEndElement();
					configWriter.WriteEndElement();
					configWriter.WriteEndDocument();
				}

				contentWriter.WriteEndDocument();
			}

			steps.AddExternalProcess(settings.VersionBuilder, settings.VersionBuilderConfigSwitch,
				"/out:" + SandcastleSettings.DefaultRawReflectionFileName).RunInBackground = true;

			if (context.ExecuteOutOfBand(steps))
			{
				context.TraceLine();
				context.TraceLine("Created reflection data for {0} version(s).", versionCount);
			}
		}

		private static void WriteContentItem(XmlWriter writer, string versionId, string value)
		{
			writer.WriteStartElement("item");
			writer.WriteAttributeString("id", versionId);
			writer.WriteValue(value);
			writer.WriteEndElement();
		}

		private static void WriteVersion(XmlWriter writer, string versionId, string reflectionFile)
		{
			writer.WriteStartElement("version");
			writer.WriteAttributeString("name", versionId);
			writer.WriteAttributeString("file", reflectionFile);
			writer.WriteEndElement();
		}

		private void AddVersionBuildSteps(SandcastleSettings settings, BuildStepCollection steps, IList<SourceVersion> sources, string outputFile)
		{
			if (sources == null || sources.Count == 0)
				return;

			List<string> assemblies = new List<string>(sources.Count);
			List<string> reflectionFiles = new List<string>(sources.Count);

			SandcastleSettings.AddSourcesInto(assemblies, true, reflectionFiles, false, sources, v => v.Source);

			if (assemblies.Count > 0)
			{
				string intermediaryFileName = outputFile + ".ver";

				steps.AddExternalProcess(settings.MRefBuilder, settings.MRefBuilderConfigSwitch,
					"/out:\"" + intermediaryFileName + "\"", settings.ScopeSwitch, settings.CreateDependencySwitch(assemblies),
					string.Join(" ", assemblies.ToArray())).RunInBackground = true;

				steps.AddExternalProcess(settings.XslTransform,
					settings.CreateXslTransformSwitch(SandcastleSettings.MergeDuplicatesTransformationFileName),
					"\"" + intermediaryFileName + "\"", "/out:\"" + outputFile + "\"");
			}

			if (reflectionFiles.Count > 0)
			{
				for (int i = 0; i < reflectionFiles.Count; i++)
				{
					string file = reflectionFiles[i];
					string target = "_" + System.IO.Path.GetFileNameWithoutExtension(file) + "_ref" + i + ".org";

					steps.AddExternalProcess(settings.XslTransform,
						settings.CreateXslTransformSwitch(SandcastleSettings.MergeDuplicatesTransformationFileName),
						"\"" + file + "\"", "/out:\"" + target + "\"");

					reflectionFiles[i] = target;
				}

				if (reflectionFiles.Count > 1)
					// NOTE: This step will include the "outputFile" if it exists, otherwise it will just merge the reflection files to a new "master" file
					steps.Add(new MergeReflectionFilesBuildStep(Engine, outputFile, reflectionFiles));
				else
					steps.AddMoveFile(reflectionFiles[0], outputFile);
			}
		}

		protected override void CancelInternal(BuildContext context)
		{
			// do nothing
		}
		#endregion
	}
}
